/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/



#include <kernel/init.h>
#include <kernel/errno.h>
#include <kernel/types.h>

#include <kernel/console.h>
#include <kernel/vsprintf.h>

#include <kernel/mm.h>
#include <kernel/pci.h>
#include <kernel/int.h>

#include <kernel/sched.h>
#include <kernel/keyboard.h>
#include <kernel/console.h>

#include <kernel/tty.h>
#include <lib/string.h>
#include <sys/syscall.h>

#include <modules/beep.h>
#include <modules/serial.h>

#include <arch/io.h>
#include <arch/sys.h>
#include <arch/x86/int.h>
#include <arch/x86/paging.h>


/* we save boot-time arguments at that address where boot was. */
struct bootarg *bootarg = (struct bootarg *) 0x7c00;
void time_init(void);


/* well, it looks like linux */

#define CMOS_READ(addr) ({		\
	outb(addr, 0x70);	\
	inb(0x71);			\
})					\

/* only for byte */

#define BCD2BIN(byte) ((byte) = ((byte) & 0xf) + ((byte) >> 4) * 10)

void time_init(void);
time_t mktime(struct time *);


/* 
 * init(void): initial all modules and devices. 
 *
 * return value: error number. main will return after initial done. 
 * if return value isn't 0, head.S will panic. else, it will halt. 
 * */

int init(void) {
	console_init();
	serial_init();

	print(red, "\nXNIX kernel starting...\n");

	int_init();
	mm_init();

	task_init();
	kbd_init();

	sti();

	printk(white, "total %d devices. \n", pci_lspci());

	printk(white, "System boots from the device %b: ", bootarg->bootdev);

	if (bootarg->bootdev & 0x80) 
		printk(white, "hard disk %b. \n", bootarg->bootdev & 7);
	
	else 
		printk(white, "floppy / flash memory / cd-rom %b. \n", bootarg->bootdev);

	time_init();

	return 0;
}

void time_init(void) {
	/* get current time, and save it to `startup_time` */
	struct time tm;

	/* it will execute when 'sec' changed for the second time */

	do {
		tm.sec = CMOS_READ(0);
		tm.min = CMOS_READ(2);
		tm.hour = CMOS_READ(4);
		tm.day = CMOS_READ(7);
		/* mouth is 1 - 12 */
		tm.month = CMOS_READ(8) - 1;
		/* this is the year only 2 bits (1970 -> 70) */

		tm.year = CMOS_READ(9);
	} while (tm.sec != CMOS_READ(0));

	BCD2BIN(tm.sec);
	BCD2BIN(tm.min);
	BCD2BIN(tm.hour);
	BCD2BIN(tm.day);
	BCD2BIN(tm.year);

	tm.year += tm.year >= 70 ? 1900 : 2000;

	printk(white, "current time: %d %d %d  %d:%d:%d. \n", 
			tm.year, tm.month + 1, tm.day, tm.hour, tm.min, tm.sec);

	printk(white, "current unix time-stamp: %d. \n", mktime(&tm));

	return;
}


static int month[] = {
	0, 
	31, 
	31 + 29, 
	31 + 29 + 31, 
	31 + 29 + 31 + 30, 
	31 + 29 + 31 + 30 + 31, 
	31 + 29 + 31 + 30 + 31 + 30, 
	31 + 29 + 31 + 30 + 31 + 30 + 31, 
	31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, 
	31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, 
	31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, 
	31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30
};


time_t mktime(struct time *tm) {
	time_t res;
	int year;

	year = tm->year - 1970;

	/* this not includes this year */
	res = year * YEAR + ((year + 1) >> 2) * DAY;
	
	/* if this year isn't a leap-year, and current-month > 2 */

	/* month starts at 0 */
	if (!isleapyear(tm->year) && tm->month > 1) 
		res -= DAY;

	res += month[tm->month] * DAY;
	res += (tm->day - 1) * DAY;
	res += tm->hour * HOUR;
	res += tm->min * MIN;
	res += tm->sec;

	return res;
}
